import c4d
import os
import sys
import re
import base64

from c4d import gui, plugins, bitmaps, utils, documents

C2U_SCRIPT_PLUGIN_ID = 1037230
SCRIPT_INFO = 1001
ANALYZE = 1002
SCRIPT_NAME = 1003

PLUGIN_ID = 1137231
DEFAULT = 1000
SCALE = 1001
CLOTH_OBJ = 1002
APPLY = 1003
WEIGHT_TAG = 1004

STRETCHING_STIFFNESS = 1005
BENDING_STIFFNESS = 1006
USE_TETHERS = 1007
USE_GRAVITY = 1008

DAMPING = 1009

EXTERNAL_ACCELERATION = 1010
RANDOM_ACCELERATION = 1011

WORLD_VELOCITY_SCALE = 1012
WORLD_ACCELERATION_SCALE = 1013

FRICTION = 1014
COLLISION_MASS_SCALE = 1015

USE_CONTINUOUS_COLLISION = 1016

USE_VIRTUAL_PARTICLES = 1017

SOLVER_FREQUENCY = 1018

SLEEP_THRESHOLD = 1019

CAPSULE_COLLIDER_LIST = 1020


def GetObjectSecurityPath(object):

    current = object
    result = current.GetName()

    while current.GetUp() != None:
        current = current.GetUp()
        result = current.GetName()+"/"+result

    match = re.compile(r'[^0-9|a-z|/]', re.IGNORECASE)
    result = match.sub('_', result, 0)
    return result

def Decode(string):
    return base64.b64decode(string)

def Encode(string):
    return base64.b64encode(string.encode("utf-8"))
  
def ClearUserData(tag):
    for id, bc in tag.GetUserDataContainer():
        del(tag[id])
        tag.RemoveUserData(id)
        
    tracks = tag.GetCTracks()
    
    for item in tracks:
        curve = item.GetCurve()
        curve.FlushKeys()
        item.Remove()

    c4d.EventAdd()

def AddUserData(obj, type, unit, name, value):
    if obj is None: return

    bc = c4d.GetCustomDataTypeDefault(type)
    bc[c4d.DESC_NAME] = name
    bc[c4d.DESC_UNIT] = unit

    element = obj.AddUserData(bc)
    if type == c4d.DTYPE_COLOR:
        colorStrCollection = value.split("_")
        r = float(colorStrCollection[0])
        g = float(colorStrCollection[1])
        b = float(colorStrCollection[2])
        color = c4d.Vector(r,g,b)
        obj[element] = color
    elif type == c4d.DTYPE_VECTOR:
        vectorStrCollection = value.split("_")
        x = float(vectorStrCollection[0])
        y = float(vectorStrCollection[1])
        z = float(vectorStrCollection[2])
        vec = c4d.Vector(x,y,z)
        obj[element] = vec
    else:
        obj[element] = value
        
    c4d.EventAdd()

def AnalyzeFieldInfo(sourceString):
    result = list()
    fields = sourceString.split(",")
    
    for item in fields:
        fieldInfo = item.split("#")

        if len(fieldInfo) < 3:
            break
        
        name = fieldInfo[0].split("-")[1]
        type = fieldInfo[1].split("-")[1]
        value = fieldInfo[2].split("-")[1]
        
        result.append((name,type,Decode(value)))

    return result

def ExecuteAnalyzeScript(node):
    str = node[SCRIPT_INFO]
    splitInfo = str.split("*")
    
    if len(splitInfo) < 2:return True
    
    node[SCRIPT_NAME] = splitInfo[0]
    
    listInfo = AnalyzeFieldInfo(splitInfo[1])
    
    ClearUserData(node)
    
    for item in listInfo:
        name,type,value = item

        if type == "bool":
            AddUserData(node, c4d.DTYPE_BOOL, c4d.DESC_UNIT_INT, name, bool(value))

        if type == "float":
            AddUserData(node, c4d.DTYPE_REAL, c4d.DESC_UNIT_FLOAT, name, float(value))

        if type == "int":
            AddUserData(node, c4d.DTYPE_LONG, c4d.DESC_UNIT_INT, name, int(value))

        if type == "vector":
            AddUserData(node, c4d.DTYPE_VECTOR, c4d.DESC_UNIT_FLOAT, name, value)

        if type == "color":
            AddUserData(node, c4d.DTYPE_COLOR, c4d.DESC_UNIT_FLOAT, name, value)

        if type == "string":
            AddUserData(node, c4d.DTYPE_STRING, None, name, value)

    node[SCRIPT_INFO] = ""
    
def CreateProxyObject(node):
    
    clothObj = node[CLOTH_OBJ]
    vertexMap = node[WEIGHT_TAG]
    
    doc = c4d.documents.GetActiveDocument()
    allPoints = clothObj.GetAllPoints()
    
    weights = vertexMap.GetAllHighlevelData()
    tempStr = ""

    matrix = c4d.utils.MatrixScale(c4d.Vector(-1,1,-1))
    
    tempStr += "Weight*"
    for index,item in enumerate(weights):
        point = allPoints[index]
        point = clothObj.GetMg() * point
        point *= node[SCALE]
        point = matrix * point

        tempStr += "{0},{1},{2},{3}".format(point.x,point.y,point.z, item)
        
        if index+1 < len(weights):
            tempStr += ";"

    tempStr += "|Stretching_Stiffness*" + str(node[STRETCHING_STIFFNESS])
    tempStr += "|Bending_Stiffness*" + str(node[BENDING_STIFFNESS])
    tempStr += "|Use_Tethers*" + (node[USE_TETHERS] == True and "True" or "False")
    tempStr += "|Use_Gravity*" + (node[USE_GRAVITY] == True and "True" or "False")
    tempStr += "|Damping*" + str(node[DAMPING])
    tempStr += "|External_Acceleration*{0},{1},{2}".format(
            node[EXTERNAL_ACCELERATION].x,
            node[EXTERNAL_ACCELERATION].y,
            node[EXTERNAL_ACCELERATION].z
            )
            
    tempStr += "|Random_Acceleration*{0},{1},{2}".format(
            node[RANDOM_ACCELERATION].x,
            node[RANDOM_ACCELERATION].y,
            node[RANDOM_ACCELERATION].z
            )
            
    tempStr += "|World_Velocity_Scale*"+str(node[WORLD_VELOCITY_SCALE])
    tempStr += "|World_Acceleration_Scale*"+str(node[WORLD_ACCELERATION_SCALE])
    tempStr += "|Friction*"+str(node[FRICTION])
    tempStr += "|Collision_Mass_Scale*"+str(node[COLLISION_MASS_SCALE])
    tempStr += "|Use_Continous_Collision*"+(node[USE_CONTINUOUS_COLLISION] == True and "True" or "False")
    tempStr += "|Use_Virtual_Particles*"+(node[USE_VIRTUAL_PARTICLES] == True and "True" or "False")
    tempStr += "|Solver_Frequency*"+str(node[SOLVER_FREQUENCY])
    tempStr += "|Sleep_Threshold*"+str(node[SLEEP_THRESHOLD])
            
    tempStr += "|Collider*"
    
    colliders = node[CAPSULE_COLLIDER_LIST]
    
    if not colliders == None:
    
        temp = list()
        for i in range(0,colliders.GetObjectCount()):
            item = colliders.ObjectFromIndex(doc, i)
            temp.append(item)
    
        for index,item in enumerate(temp):
        
            tempStr += Encode(GetObjectSecurityPath(item))
        
            if index+1 < len(temp):
                tempStr += ";"

    script = clothObj.GetTag(C2U_SCRIPT_PLUGIN_ID)
    
    if not script == None:
        clothObj.KillTag(C2U_SCRIPT_PLUGIN_ID)
    
    clothObj.InsertTag(c4d.BaseTag(C2U_SCRIPT_PLUGIN_ID))
    tag = clothObj.GetTag(C2U_SCRIPT_PLUGIN_ID)

    tag[SCRIPT_INFO] = "C2UStaticInfoUnityCloth*Name-m_Enabled#Type-bool#Value-True,Name-content#Type-string#Value-"+Encode(tempStr)
    
    ExecuteAnalyzeScript(tag)

class Entry(plugins.TagData):

    def Init(self, node):
    
        node[SCALE] = 1
        
        node[STRETCHING_STIFFNESS] = 1
        node[BENDING_STIFFNESS] = 0
        node[USE_TETHERS] = True
        node[USE_GRAVITY] = True
        node[DAMPING] = 0
        node[EXTERNAL_ACCELERATION] = c4d.Vector(0,0,0)
        node[RANDOM_ACCELERATION] = c4d.Vector(0,0,0)
        node[WORLD_VELOCITY_SCALE] = 0.5
        node[WORLD_ACCELERATION_SCALE] = 1
        node[FRICTION] = 0.5
        node[COLLISION_MASS_SCALE] = 0
        node[USE_CONTINUOUS_COLLISION] = False
        node[USE_VIRTUAL_PARTICLES] = False
        node[SOLVER_FREQUENCY] = 120.0
        node[SLEEP_THRESHOLD] = 0.1
    
        return True

    def Message(self, node, type, data):
    
        if type==c4d.MSG_DESCRIPTION_COMMAND:
        
            bt = data['id'][0].id 
            doc = c4d.documents.GetActiveDocument()
        
            if bt == APPLY:
            
                vertexMap = node[WEIGHT_TAG]
                
                if not vertexMap.CheckType(c4d.Tvertexmap):
                    node[WEIGHT_TAG] = None
                    gui.MessageDialog("Please Set 'Vertex map' Tag!")
                    return True
                
                CreateProxyObject(node)
            
        return True
        
if __name__ == "__main__":
    bmp = bitmaps.BaseBitmap()
    dir, file = os.path.split(__file__)
    fn = os.path.join(dir, "res", "tag.png")
    bmp.InitWith(fn)
    
    plugins.RegisterTagPlugin(id = PLUGIN_ID, str="Unity Cloth Tag",
                              info = c4d.TAG_VISIBLE, g=Entry,
                              description = "unityclothtag", icon=bmp)